﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// Representation of a numeric value (signed, unsigned, or float)
	/// </summary>
	[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
	internal struct NumericValue
	{
		/// <summary>
		/// Value if the type is a signed number.
		/// </summary>
		[System.Runtime.InteropServices.FieldOffset(0)]
		private readonly long m_longvalue;

		/// <summary>
		/// Value if the type is an unsigned number.
		/// </summary>
		[System.Runtime.InteropServices.FieldOffset(0)]
		private readonly ulong m_ulongvalue;

		/// <summary>
		/// Value if the type is a float.
		/// </summary>
		[System.Runtime.InteropServices.FieldOffset(0)]
		private readonly double m_floatvalue;

		/// <summary>
		/// The type of the numeric value.
		/// </summary>
		[System.Runtime.InteropServices.FieldOffset(8)]
		internal readonly IRPrimitiveNumericType ValType;

		private NumericValue(long lv, IRPrimitiveNumericType pnvt)
		{
			this.ValType = pnvt;

			this.m_longvalue = 0;
			this.m_ulongvalue = 0;
			this.m_floatvalue = 0.0;

			this.m_longvalue = lv; 
		}

		private NumericValue(ulong ulv, IRPrimitiveNumericType pnvt)
		{
			this.ValType = pnvt;

			this.m_longvalue = 0;
			this.m_ulongvalue = 0;
			this.m_floatvalue = 0.0;

			this.m_ulongvalue = ulv;
		}

		private NumericValue(double dv, IRPrimitiveNumericType pnvt)
		{
			this.ValType = pnvt;

			this.m_longvalue = 0;
			this.m_ulongvalue = 0;
			this.m_floatvalue = 0.0;

			this.m_floatvalue = dv;
		}

		public override string ToString()
		{
			if(this.ValType == null)
				return "Invalid!!";
			else if(this.ValType is IRSignedIntegerType)
				return String.Format("{0}", this.m_longvalue);
			else if(this.ValType is IRUnSignedIntegerType)
				return String.Format("{0}U", this.m_ulongvalue);
			else
				return String.Format("{0}", this.m_floatvalue);
		}

		/// <summary>
		/// The value formatted as a string for display in the debugger.
		/// </summary>
		internal string DebuggerString()
		{
			Debug.Assert(this.ValType != null);

			if(this.ValType is IRSignedIntegerType)
				return String.Format("{0}", this.m_longvalue);
			else if(this.ValType is IRUnSignedIntegerType)
				return String.Format("{0}U", this.m_ulongvalue);
			else
				return String.Format("{0}", this.m_floatvalue);
		}

		/// <summary>
		/// The value formatted as a string in a format for emitting in the trace.
		/// </summary>
		internal string EmitForTrace()
		{
			Debug.Assert(this.ValType != null);

			if(this.ValType is IRSignedIntegerType)
				return this.m_longvalue.ToString();
			else if(this.ValType is IRUnSignedIntegerType)
				return this.m_ulongvalue.ToString();
			else
				return this.m_floatvalue.ToString();
		}

		/// <summary>
		/// The value formatted and output for a heap dump.
		/// </summary>
		internal void SerializeForHeapDump(System.IO.TextWriter dumpstr)
		{
			Debug.Assert(this.ValType != null);

			if(this.ValType is IRSignedIntegerType)
				dumpstr.Write(this.m_longvalue);
			else if(this.ValType is IRUnSignedIntegerType)
				dumpstr.Write(this.m_ulongvalue);
			else
				dumpstr.Write(this.m_floatvalue);
		}

		/// <summary>
		/// Parse a numeric value of the given type from a string.
		/// </summary>
		internal static NumericValue ParseValue(IRPrimitiveNumericType nvt, string val)
		{
			if(nvt is IRSignedIntegerType)
				return new NumericValue(Int64.Parse(val), nvt);
			else if(nvt is IRUnSignedIntegerType)
				return new NumericValue(UInt64.Parse(val), nvt);
			else
				return new NumericValue(Double.Parse(val), nvt);
		}

		///////////////////////////////////////////////////////////////////////////////
		//Big block of constructors and accessors

		internal static NumericValue MakeZeroValueOfType(IRPrimitiveNumericType pnt)
		{ return new NumericValue(0U, pnt); }

		internal static NumericValue MakeSignedValueOfType(long val, IRSignedIntegerType oftype)
		{
			switch(oftype.SignedTag)
			{
				case IRSignedIntegerType.SignedTypeTag.BoolTag:
					{
						if((val < 0) | (1 < val))
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				case IRSignedIntegerType.SignedTypeTag.SByteTag:
					{
						if((val < SByte.MinValue) | (SByte.MaxValue < val))
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				case IRSignedIntegerType.SignedTypeTag.Int16Tag:
					{
						if((val < Int16.MinValue) | (Int16.MaxValue < val))
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				case IRSignedIntegerType.SignedTypeTag.Int32Tag:
					{
						if((val < Int32.MinValue) | (Int32.MaxValue < val))
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				default:
					{
						return new NumericValue(val, oftype);
					}
			}
		}

		internal long ReadSignedValue()
		{
			Debug.Assert(this.ValType is IRSignedIntegerType);

			return this.m_longvalue;
		}

		internal static NumericValue MakeUnSignedValueOfType(ulong val, IRUnSignedIntegerType oftype)
		{
			switch(oftype.UnSignedTag)
			{
				case IRUnSignedIntegerType.UnsignedTypeTag.ByteTag:
					{
						if(Byte.MaxValue < val)
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				case IRUnSignedIntegerType.UnsignedTypeTag.CharTag:
					{
						if(Char.MaxValue < val)
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				case IRUnSignedIntegerType.UnsignedTypeTag.UInt16Tag:
					{
						if(UInt16.MaxValue < val)
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				case IRUnSignedIntegerType.UnsignedTypeTag.UInt32Tag:
					{
						if(UInt32.MaxValue < val)
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				default:
					{
						return new NumericValue(val, oftype);
					}
			}
		}

		internal ulong ReadUnSignedValue()
		{
			Debug.Assert(this.ValType is IRUnSignedIntegerType);

			return this.m_ulongvalue;
		}

		internal static NumericValue MakeFloatValueOfType(double val, IRFloatingPointType oftype)
		{
			switch(oftype.FloatingPointTag)
			{
				case IRFloatingPointType.FloatingPointTypeTag.SingleTag:
					{
						if((val < Single.MinValue) | (Single.MaxValue < val))
							throw new InterpRuntimeException("Numeric Range");
						return new NumericValue(val, oftype);
					}
				default:
					{
						return new NumericValue(val, oftype);
					}
			}
		}

		internal double ReadFloatValue()
		{
			Debug.Assert(this.ValType is IRFloatingPointType);

			return this.m_floatvalue;
		}
	}
}

